RustのDI |
您所在的位置:网站首页 › laura rust di linkedin › RustのDI |
κeenです。Rustその2 Advent Calendar 2017が空いてたので小ネタをば。1日目の記事です。 Dependency Injectionの話。 例えば3層アーキテクチャで組む時には以下のような依存関係が発生します。 [user interface(web)] | V [logic(service)] | V [database access(dao)]これを他の実装に依存せずにそれぞれの層を書きたい、どうしようという問題設定です。 ナイーブな実装簡単に考えたらRustならトレイトで抽象化してあげればよさそうです。 コードにするなら共通インターフェイスにトレイトを定義してあげて pub trait UserDao { fn find_user(&self, id: i32) -> Result; }Dao側は実際のDBに合わせた実装、 struct UserPgDao(PgConnection); impl UserDao for UserPgDao { fn find_user(&self, id: i32) -> Result { // ... } }そしてサービスレイヤはトレイトにのみ依存した実装をかけば1丁あがりです。 struct UserService(U); impl UserService { pub fn get_user_by_id(&self, id: i32) -> Result { self.0.find_user(id) } } 問題点シンプルなものなら上のもので問題ありません。問題は複雑な依存関係が発生したときに起こります。 [Web] / \ / \ [GroupService] [UserService] | \ | | \ | | \ | | \ | | \ | [GroupDao] [UserDao]ここでのUserDaoへの依存のように複数からの依存関係があるとRustの所有権機能によって簡単にはコードを書けなくなります。 解決策は色々とあろうかと思いますが、ここではその一つ、Cake Patternの亜種によって解決したいと思います。 RustでCake PatternCake PatternはScala由来のDIパターンで、Scala界隈ではそれなりに使われているように思います。 詳しくは 実戦での Scala: Cake パターンを用いた Dependency Injection (DI) をあたって下さい。以下、Cake Patternは既知のものとして話を進めます。 幸いなことにCake Patternで必要とされる言語機能はRustにも対応するものがある程度揃っているのでRustに翻訳できます。 Scala Rust トレイト トレイト 自分型アノテーション トレイトの継承 class in class モジュール 抽象メンバー getterメソッドこの対応で翻訳してあげるとDaoとそのComponentはこうなり、 // 上述記事中のUserRepository相当 pub trait UserDao {/* ... */} // 上述記事中のUserRepositoryComponent相当 pub trait HaveUserDao { type UserDao: UserDao; fn user_dao(&self) -> Self::UserDao; }ServiceとそのComponentはこうなります。 // 上述記事中のUserService相当 trait UserService: HaveUserDao { pub fn get_user_by_id(&self, id: i32) -> Result { self.user_service().find_user(id) } } // UserServiceはHaveUserDaoにのみ依存するのでそれさえ実装していれば自動で実装を与えられます。 // もちろんテストなどで挙動を上書きしたければ具体的な型での実装で上書きできます。 impl UserService for T {} // 上述記事中のUserServiceComponent相当 trait HaveUserService { type UserService: UserService; fn user_service(&self) -> Self::UserService; }これで UserService が UserDao を専有しなくなりました。 同じように Group{Dao,Service} も同じようにつくってあげます。 するとサーバが以下のようにかけます。 struct Server { user_dao: UserPgDao, group_dao: GroupPgDao, } impl HaveUserDao for Server { type UserDao = UserPgDao; fn user_dao(&self) -> Self::UserDao { &self.user_dao } } impl HaveUserService for Server{ type UserService = Self; fn user_service(&self) -> Self::UserService { self } } // 同じくGroupもServer の依存する user_dao と gropu_dao も型パラメータで抽象化できますが、実際にその抽象化が必要になることはないでしょう。 DaoからServerまで矛盾なくコードが書けたので晴れて複依存問題が解決出来ました。以上小ネタでした。 他の解決策 Daoをコピーする複数回必要なら複数用意すればいいじゃないという発想。 悪くはないんですが例えばDaoでクエリ結果のキャッシュを持ちたい場合などに不都合です。 Daoを参照で持つ悪くはないんですがライフタイムパラメータが増えて型が煩雑になります。 DaoをArcで持つHTTPフレームワークが大抵Syncを要求してくるのでRcではだめで、Arcです。 これでもいいと思います。 結びに業務でDIするにあたっていい案もなかったので前職でのScalaの経験からCake Patternを使ってみたらとりあえずできたという感じです。 めちゃくちゃ便利という訳でもないですが今の所問題もないのでそのまま使っているのが現状で、誰か他の知見を下さい。 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |